/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.fellowera.framework;

import com.google.common.base.Objects;
 

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;


/**
 * 
 * @author oyku
 */
public abstract class CriteriaApiQuery<T extends BaseEntity> {

	@PersistenceContext
	private EntityManager em;
	// results
	private List<T> resultList;
	private T singleResult;
	// paging
	private Integer firstResult;
	private Integer maxResults;
	private Long resultCount;
	private String order;
	private CriteriaQuery<T> qSelected;
	
	public CriteriaApiQuery() {
	}

	// ************************************************************************************************************************
	public EntityManager getEntityManager() {
		return em;
	}

	// ************************************************************************************************************************
	// Result lists
	public List<T> getResultList() {
		if (isAnyParameterDirty()) {
			refresh();
		}
		initResultList();
		return truncResultList(resultList);
	}

	public T getSingleResult() {
		if (isAnyParameterDirty()) {
			refresh();
		}
		initSingleResult();
		return singleResult;
	}

	public Long getResultCount() {
		if (isAnyParameterDirty()) {
			refresh();
		}
		initResultCount();
		return resultCount;
	}

	// ************************************************************************************************************************
	// getters/setters
	public Integer getFirstResult() {
		return firstResult;
	}

	public void setFirstResult(Integer firstResult) {
		this.firstResult = firstResult;
		refresh();
	}

	public Integer getMaxResults() {
		return maxResults;
	}

	public void setMaxResults(Integer maxResults) {
		this.maxResults = maxResults;
		refresh();
	}

	// ************************************************************************************************************************
	// Pagination
	public boolean isNextExists() {
		return resultList != null && getMaxResults() != null
				&& resultList.size() > getMaxResults();
	}

	public boolean isPreviousExists() {
		return getFirstResult() != null && getFirstResult() != 0;
	}

	public boolean isPaginated() {
		return isNextExists() || isPreviousExists();
	}

	public void first() {
		setFirstResult(0);
	}

	public void previous() {
		setFirstResult(getPreviousFirstResult());
	}

	public void next() {
		setFirstResult(getNextFirstResult());
	}

	public void last() {
		setFirstResult(getLastFirstResult().intValue());
	}

	// ************************************************************************************************************************
	// Paginator value assignments
	public Integer getPageCount() {
		if (getMaxResults() == null) {
			return null;
		} else {
			int rc = getResultCount().intValue();
			int mr = getMaxResults().intValue();
			int pages = rc / mr;
			return rc % mr == 0 ? pages : pages + 1;
		}
	}

	public Long getLastFirstResult() {
		Integer pc = getPageCount();
		return pc == null ? null : (pc.longValue() - 1) * getMaxResults();
	}

	public int getPreviousFirstResult() {
		return getMaxResults() >= (getFirstResult() == null ? 0
				: getFirstResult()) ? 0 : getFirstResult() - getMaxResults();
	}

	public int getNextFirstResult() {
		return (getFirstResult() == null ? 0 : getFirstResult())
				+ getMaxResults();
	}

	public void refresh() {
		resultCount = null;
		resultList = null;
		singleResult = null;
		 
	}

	// ************************************************************************************************************************
	private static boolean isAnyParameterDirty() {
		// order by
		// criteria
		return false;
	}

	private CriteriaBuilder criteriaBuilder;
	private CriteriaQuery<T> criteriaQuery;
	protected Root<T> criteriaRoot;
	private Predicate criteria;

	protected CriteriaBuilder getCriteriaBuilder() {
		 CriteriaBuilder cb = em.getCriteriaBuilder();
		 this.criteriaBuilder=cb;
		return this.criteriaBuilder;
	}

	protected Root<T> getCriteriaRoot() {
	   Root<T> r = getCriteriaQuery().from(getClassType());
		this.criteriaRoot = r;
		return this.criteriaRoot;
	}

	protected CriteriaQuery<T> getCriteriaQuery() {
		 
		  CriteriaQuery<T> cq = getCriteriaBuilder().createQuery(getClassType());
		  this.criteriaQuery=cq;
		 return this.criteriaQuery;
	}

	protected Predicate getCriteria() {
		return this.criteria;
	}

	protected void setCriteria(Predicate criteria) {
		this.criteria = criteria;
		refresh();
	}

	private Order criteriaOrder;

	private Order getCriteriaOrder() {
		return criteriaOrder;
	}

	private void setCriteriaOrder(Order sortOrder) {
		this.criteriaOrder = sortOrder;
		refresh();
	}

	public String getOrder() {
		return order;
	}
	
	public void setOrder(String order) {
		this.order = order;
		String[] parts = order.split(" ");
		try {
			String fieldName = parts[0];
			String direction = "asc";
			if (direction.equals("asc")) {
				setCriteriaOrder(getCriteriaBuilder().desc(
						getCriteriaRoot().get(fieldName)));
			} else  {
				setCriteriaOrder(getCriteriaBuilder().asc(
						getCriteriaRoot().get(fieldName)));
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			// silently ignore
		}

	}

	protected Query getQuery() {
		// CriteriaBuilder cb = em.getCriteriaBuilder();
		// CriteriaQuery<T> cq = cb.createQuery(getClassType());
		// Root<T> r = cq.from(getClassType());
		// cq.select(r);
		// cq.where(cb.like(r.<String>get("siteAddress"), "q%"));
		// SELECT FROM
		if (qSelected == null) {
			qSelected = getCriteriaQuery().select(getCriteriaRoot()).distinct(
					true);
		}

		// WHERE
		if (getCriteria() != null) {
			getCriteriaQuery().where(getCriteria());
		}
		// ORDER BY
		if (getCriteriaOrder() != null) {
			// getCriteriaQuery().orderBy(getCriteriaBuilder().asc(getCriteriaRoot().get("siteAddress")));
			getCriteriaQuery().orderBy(getCriteriaOrder());
		}
		Query q = em.createQuery(getCriteriaQuery());
		return q;
	}

	@SuppressWarnings("unchecked")
	private void initResultList() {
		if (resultList == null) {
			Query q = getQuery();
			if (getFirstResult() != null) {
				q.setFirstResult(getFirstResult());
			}
			if (getMaxResults() != null) {
				q.setMaxResults(getMaxResults() + 1);
			}
			resultList = q == null ? null : q.getResultList();
		}
	}

	@SuppressWarnings("unchecked")
	private void initSingleResult() {
		if (singleResult == null) {
			Query q = getQuery();
			singleResult = (T) (q == null ? null : q.getSingleResult());
		}
	}

	private void initResultCount() {
		if (resultCount == null) {
			// CriteriaBuilder cb = em.getCriteriaBuilder();
			CriteriaQuery<Long> cq = getCriteriaBuilder().createQuery(
					Long.class);

			Root<T> r = cq.from(getClassType());
			cq.select(getCriteriaBuilder().count(r));
			// apply criteria if available, no need to apply sort order for
			// count
			if (getCriteria() != null) {
				cq.where(getCriteria());
			}

			Query q = em.createQuery(cq);

			resultCount = (Long) (q == null ? null : q.getSingleResult());
		}
	}

	private List<T> truncResultList(List<T> results) {
		Integer mr = getMaxResults();
		if (mr != null && results.size() > mr) {
			return results.subList(0, mr);
		} else {
			return results;
		}
	}

	// applicable for only classes directly inheriting from this class
	protected abstract Class<T> getClassType();
	// {
	// return (Class<T>) ((ParameterizedType) getClass()
	// .getGenericSuperclass()).getActualTypeArguments()[0];
	// }
	
}
